package com.newtouch.bxzs.business.config;

import cn.hutool.core.collection.CollectionUtil;
import com.newtouch.bxzs.business.enclosure.mapper.LogErrorMapper;
import com.newtouch.bxzs.business.enclosure.mapper.LogTransactionMapper;
import com.newtouch.bxzs.common.base.BaseErrorEnum;
import com.newtouch.bxzs.common.base.BaseException;
import com.newtouch.bxzs.common.base.model.LogError;
import com.newtouch.bxzs.common.base.model.LogTransaction;
import com.newtouch.bxzs.common.base.util.IpUtil;
import com.newtouch.bxzs.common.base.util.RedisUtils;
import com.newtouch.bxzs.common.base.util.UserDetailUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

/**
 * 日志切面处理类
 *
 * <p>
 * <b>History:</b>
 * <table border="1">
 * <tr>
 * <th>Date</th>
 * <th>Operator</th>
 * <th>Memo</th>
 * </tr>
 * <tr>
 * <td>2021年2月5日</td>
 * <td>Amour</td>
 * <td>Create</td>
 * </tr>
 * </table>
 * 
 * @author Amour
 * 
 * @version 1.0.0
 * @since 1.0.0
 */
@Aspect
@Component
public class SysLogAspect {

	@Autowired
	LogTransactionMapper logTransactionMapper;

	@Autowired
	LogErrorMapper logErrorMapper;

	@Autowired
	private RedisUtils redisUtils;

	/** 交易日志批量存储集合 */
	private List<LogTransaction> logTransactionList = new ArrayList<>();

	/** 交易日志批量存储数量 */
	@Value("${transaction.log.number}")
	private Integer LOG_TRANSACTION_NUMBER;

	/** 记录操作耗时 */
	private ThreadLocal<Long> startTime = new ThreadLocal<>();

	/**
	 * 设置操作日志切入点,记录操作日志,在注解的位置切入代码
	 */
	@Pointcut("@annotation(com.newtouch.bxzs.business.config.MyLog)")
	public void logPoinCut() {

	}

	/**
	 * 设置操作异常切入点记录异常日志 扫描所有包下操作
	 */
	@Pointcut("execution(* com.newtouch.bxzs.business..*.*(..))")
	public void exceptionLogPoinCut() {

	}

	@Before("logPoinCut()")
	public void doBefore(JoinPoint joinPoint) throws Throwable {
		// 读取缓存
		final Jwt jwt = (Jwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
		if (jwt == null) {
			throw new BaseException(BaseErrorEnum.USER_INVALID);
		}
		String token = jwt.getTokenValue();
		if (StringUtils.isEmpty(token)) {
			throw new BaseException(BaseErrorEnum.USER_INVALID);
		}
		String redisToken = redisUtils.get(token);
		if (!StringUtils.isEmpty(redisToken)) {
			throw new BaseException(BaseErrorEnum.USER_INVALID);
		}
		startTime.set(System.currentTimeMillis());
	}

	@AfterReturning("logPoinCut()")
	public void saveSysLog(JoinPoint joinPoint) {
		// 从切面织入点处通过反射机制获取织入点处的方法
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		// 获取切入点所在的方法
		Method method = signature.getMethod();
		this.insertLogTransaction(method, true);
	}

	@AfterThrowing(pointcut = "exceptionLogPoinCut()", throwing = "e")
	public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
		// 从切面织入点处通过反射机制获取织入点处的方法
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		// 获取切入点所在的方法
		Method method = signature.getMethod();
		// 异常时也需记录交易日志
		this.insertLogTransaction(method, false);
		LogError logError = new LogError();
		logError.setErrorClassName(joinPoint.getTarget().getClass().getName());
		logError.setErrorMethodName(method.getName());
		logError.setErrorOccurrenceTime(new Date());
		logError.setErrorTypeName(e.getClass().getTypeName());
		// 异常描述过长时，需要截取
		String errorDescription = e.getLocalizedMessage();
		if (!StringUtils.isEmpty(errorDescription) && errorDescription.length() >= 3000) {
			errorDescription = errorDescription.substring(0, 3000);
		}
		logError.setErrorDescription(errorDescription);
		String ip = "";
		try {
			ip = IpUtil.getIp();
		} catch (Exception e2) {
			e2.printStackTrace();
		} finally {
			if (StringUtils.isEmpty(ip)) {
				return;
			}
			logError.setIpAddress(ip);
		}
		logErrorMapper.insert(logError);
	}

	/**
	 * 保存交易日志信息
	 * 
	 * @param method
	 *            发生日志的方法
	 * @param isSuccess
	 *            是否成功
	 * @param realSave
	 *            是否立即存储
	 */
	private void insertLogTransaction(Method method, Boolean isSuccess) {
		// 获取操作
		MyLog myLog = method.getAnnotation(MyLog.class);
		if (myLog != null) {
			LogTransaction logTransaction = new LogTransaction();
			logTransaction.setUserId(UserDetailUtil.getUserDetail().getUserId());
			logTransaction.setUserOperationTime(new Date());
			logTransaction.setIsSuccess(isSuccess);
			logTransaction.setSystemFunctionName(myLog.systemFunctionName());
			logTransaction.setSystemFunctionUrl(myLog.systemFunctionUrl());
			logTransaction.setUserOperationType(myLog.userOperationType());
			logTransaction.setUserOperationSummary(myLog.userOperationSummary());
			Long systemExecuteTime = System.currentTimeMillis() - startTime.get();
			logTransaction.setSystemExecuteTime(systemExecuteTime.intValue());
			// 异常日志的交易日志需要立即存储
			if (!isSuccess) {
				logTransactionMapper.insert(logTransaction);
			} else {
				// 判断集合是否满足存储条件,若满足则进行insert并清空集合
				this.checkLogTransactionList();
				// 普通交易日志批量存储
				logTransactionList.add(logTransaction);
			}
		}
	}

	private void checkLogTransactionList() {
		synchronized (logTransactionList) {
			if (!CollectionUtil.isEmpty(logTransactionList) && logTransactionList.size() >= LOG_TRANSACTION_NUMBER) {
				Integer insertNumber = logTransactionMapper.insertLogTransaction(logTransactionList);
				if (insertNumber == logTransactionList.size()) {
					// 批量插入后清空集合中的数据
					logTransactionList.clear();
				}
			}
		}
	}
}
